//
//  File.swift
//  
//
//  Created by czi on 2023/12/8.
//

import Foundation
import Combine

public protocol Service {
    associatedtype ExecutorImpl: Executor
    associatedtype RequestImpl: Request
    associatedtype ParserImpl: Parser

    var request: RequestImpl { get }
    var executer: ExecutorImpl { get }
    var parser: ParserImpl { get }

    func request() async throws -> ParserImpl.Payload
    func request(completionHandler: @escaping @Sendable (ParserImpl.Payload?, Error?) -> Void)
}

public extension Service {
    @discardableResult
    func request() async throws -> ParserImpl.Payload {
        let urlRequest = try request.asURLRequest()
        let (data, response) = try await executer.execute(urlRequest)
        guard (200..<300).contains(response.statusCode) else {
            throw LSResponseError.wrongResponse(response)
        }
        return try parser.parse(data)
    }
    
    func request(completionHandler: @escaping @Sendable (ParserImpl.Payload?, Error?) -> Void) {
        do {
            let urlRequest = try request.asURLRequest()
            executer.execute(urlRequest) { [parser] data, response, error in
                if let error {
                    completionHandler(nil, error)
                } else if let data, let response {
                    guard (200..<300).contains(response.statusCode) else {
                        completionHandler(nil, LSResponseError.wrongResponse(response))
                        return
                    }
                    do {
                        let payload = try parser.parse(data)
                        completionHandler(payload, nil)
                    } catch {
                        completionHandler(nil, LSResponseError.parserError(error.localizedDescription))
                    }
                }
            }
        } catch {
            completionHandler(nil, LSResponseError.badRequest)
        }
    }

    @discardableResult
    func justRequest() throws -> URLSessionDataTask {
        let urlRequest = try request.asURLRequest()
        return executer.execute(urlRequest)
    }
    
    func asPublisher() -> AnyPublisher<ParserImpl.Payload, Error> {
        do {
            let request = try request.asURLRequest()
            return URLSession.shared
                .dataTaskPublisher(for: request)
                .tryMap {
                    let response = $0.response as! HTTPURLResponse
                    if (200..<300).contains(response.statusCode) {
                        return try parser.parse($0.data)
                    }
                    throw LSResponseError.wrongResponse(response)
                }
                .receive(on: DispatchQueue.main)
                .eraseToAnyPublisher()
        } catch {
            return Fail(error: LSResponseError.badRequest)
                .receive(on: DispatchQueue.main)
                .eraseToAnyPublisher()
        }
    }
}

enum LSResponseError: LocalizedError {
    case wrongResponse(HTTPURLResponse)
    case badRequest
    case parserError(String)
    
    var errorDescription: String? {
        switch self {
        case .wrongResponse(let hTTPURLResponse):
            return HTTPURLResponse.localizedString(forStatusCode: hTTPURLResponse.statusCode)
        case .badRequest:
            return "bad request"
        case .parserError(let msg):
            return msg
        }
    }
}
